package mypackage;

import ch.qos.logback.classic.pattern.DateConverter;
import org.springframework.util.StringUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

public class TimestampUtils {

    public static final String DATE_PATTERN = "yyyy-MM-dd";

    public static final String DATE_PATTERN_MOUTH = "yyyy-MM";

    public static final String DATE_PATTERN_YEAR = "yyyy";

    public static final String DATE_TIME_PATTERN = "yyyy-MM-dd hh:mm:ss";

    public static final String UNIT_SECOND = "second";

    public static final String UNIT_MINUTE = "minute";

    public static final String UNIT_HOUR = "hour";

    public static final String UNIT_DAY = "day";

    public static final String UNIT_WEEK = "week";

    public static final String UNIT_MONTH = "month";

    public static final String UNIT_YEAR = "year";

    public static final String UTC_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";

    public static final String UTC_DATE_TIME_PATTERN_000000 = "yyyy-MM-dd'T'HH:mm:ss.000000";


    public static long calNextTime(String unit, int period) {
        ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneOffset.UTC);
        if (StringUtils.isEmpty(unit)) {
            return zonedDateTime.toInstant().toEpochMilli();
        }
        switch (unit) {
            case UNIT_HOUR:
                return zonedDateTime.plus(period, ChronoUnit.HOURS).toInstant().toEpochMilli();
            case UNIT_DAY:
                return zonedDateTime.plus(period, ChronoUnit.DAYS).toInstant().toEpochMilli();
            case UNIT_WEEK:
                return zonedDateTime.plus(period, ChronoUnit.WEEKS).toInstant().toEpochMilli();
            case UNIT_MONTH:
                return zonedDateTime.plus(period, ChronoUnit.MONTHS).toInstant().toEpochMilli();
            case UNIT_YEAR:
                return zonedDateTime.plus(period, ChronoUnit.YEARS).toInstant().toEpochMilli();
            default:
                return zonedDateTime.plus(period, ChronoUnit.MONTHS).toInstant().toEpochMilli();
        }
    }

    public static long calBeforeTime(String unit, int period) {
        ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneOffset.UTC);
        if (StringUtils.isEmpty(unit)) {
            return zonedDateTime.toInstant().toEpochMilli();
        }
        switch (unit) {
            case UNIT_HOUR:
                return zonedDateTime.minus(period, ChronoUnit.HOURS).toInstant().toEpochMilli();
            case UNIT_DAY:
                return zonedDateTime.minus(period, ChronoUnit.DAYS).toInstant().toEpochMilli();
            case UNIT_WEEK:
                return zonedDateTime.minus(period, ChronoUnit.WEEKS).toInstant().toEpochMilli();
            case UNIT_MONTH:
                return zonedDateTime.minus(period, ChronoUnit.MONTHS).toInstant().toEpochMilli();
            case UNIT_YEAR:
                return zonedDateTime.minus(period, ChronoUnit.YEARS).toInstant().toEpochMilli();
            default:
                return zonedDateTime.minus(period, ChronoUnit.MONTHS).toInstant().toEpochMilli();
        }
    }


    public static long calNextTime(Long from, String unit, int period) {
        ZonedDateTime zonedDateTime = Instant.ofEpochMilli(from).atZone(ZoneOffset.UTC);
        if (StringUtils.isEmpty(unit)) {
            return zonedDateTime.toInstant().toEpochMilli();
        }
        switch (unit) {
            case UNIT_DAY:
                return zonedDateTime.plus(period, ChronoUnit.DAYS).toInstant().toEpochMilli();
            case UNIT_WEEK:
                return zonedDateTime.plus(period, ChronoUnit.WEEKS).toInstant().toEpochMilli();
            case UNIT_MONTH:
                return zonedDateTime.plus(period, ChronoUnit.MONTHS).toInstant().toEpochMilli();
            case UNIT_YEAR:
                return zonedDateTime.plus(period, ChronoUnit.YEARS).toInstant().toEpochMilli();
            default:
                return zonedDateTime.plus(period, ChronoUnit.MONTHS).toInstant().toEpochMilli();
        }
    }

    /**
     * 计算两个时间差（年，月，星期，日，时，分，秒）
     *
     * @param beginDate
     * @param endDate
     * @return
     */
    public static Long calculateTimeDifference(String unit, Date beginDate, Date endDate) {
        if (null == beginDate || null == endDate) {
            return 0L;
        }
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDateTime fromDateTime = LocalDateTime.ofInstant(beginDate.toInstant(), zoneId);
        LocalDateTime toDateTime = LocalDateTime.ofInstant(endDate.toInstant(), zoneId);

        LocalDateTime tempDateTime = LocalDateTime.from(fromDateTime);

        switch (unit) {
            case UNIT_SECOND:
                return tempDateTime.until(toDateTime, ChronoUnit.SECONDS);
            case UNIT_HOUR:
                return tempDateTime.until(toDateTime, ChronoUnit.HOURS);
            case UNIT_DAY:
                return tempDateTime.until(toDateTime, ChronoUnit.DAYS);
            case UNIT_WEEK:
                return tempDateTime.until(toDateTime, ChronoUnit.WEEKS);
            case UNIT_MONTH:
                return tempDateTime.until(toDateTime, ChronoUnit.MONTHS);
            case UNIT_YEAR:
                return tempDateTime.until(toDateTime, ChronoUnit.YEARS);
            default:
                return 0L;
        }
    }

    /**
     * 获取指定日期的（年，月，星期，日，时，分，秒）
     *
     * @param date
     * @return
     */
    public static String dateToStrByUnit(String unit, Date date) {
        if (null == date) {
            return "";
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        LocalDate localDate = zonedDateTime.toLocalDate();
        String format = localDate.format(DateTimeFormatter.ofPattern(getDatePatternByUnit(unit)));
        System.out.println(format);

        return ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ofPattern(getDatePatternByUnit(unit)));
    }

    private static String getDatePatternByUnit(String unit) {
        switch (unit) {
            case UNIT_DAY:
                return DATE_PATTERN;
            case UNIT_MONTH:
                return DATE_PATTERN_MOUTH;
            case UNIT_YEAR:
                return DATE_PATTERN_YEAR;
            default:
                return "";
        }
    }

    public static String formatEpochInMillis(long epochInMillis, ZoneId zoneId) {
        if (epochInMillis == 0) {
            epochInMillis = Instant.now(Clock.system(ZoneOffset.UTC)).toEpochMilli();
        }
        if (zoneId == null) {
            zoneId = ZoneId.of("UTC");
        }
        Instant i = Instant.ofEpochMilli(epochInMillis);
        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(i, zoneId);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
        return zonedDateTime.format(formatter);
    }

    public static long dateToBeginEpochInMillis(Date date) {
        return ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).withHour(0).withMinute(0).withSecond(0).toInstant().toEpochMilli();
    }

    public static long dateToEndEpochInMillis(Date date) {
        return ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).withHour(23).withMinute(23).withSecond(59).toInstant().toEpochMilli();
    }

    public static String epochInMillisToDateStr(long epochInMillis) {
        return ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochInMillis), ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ofPattern(DATE_PATTERN));
    }

    public static String dateToStr(Date date) {
        return dateToStrByUnit(UNIT_DAY, date);
    }

    public static Date strToDate(String dateStr) {
        return Date.from(LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(DATE_PATTERN)).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
    }

    public static Date strToDateByUnit(String unit, String dateStr) {
        return Date.from(LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(getDatePatternByUnit(unit))).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 获取两个日期相差的天数
     *
     * @param beginDate 日期
     * @param endDate   另一个日期
     * @return 相差天数
     */
    public static int getIntervalDays(Date beginDate, Date endDate) {
        long time = Math.abs(endDate.getTime() - beginDate.getTime());
        return (int) time / (24 * 60 * 60 * 1000);
    }

    /**
     * 获取指定日期的（年，月，星期，日，时，分，秒）
     *
     * @param date
     * @return
     */
    public static int getCalculateTimeUnitValue(String unit, Date date) {
        if (null == date) {
            return 0;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);

        return calendar.get(getCalendarUnitByUnit(unit));
    }

    /**
     * 获取时间范围内的
     *
     * @param beginDate
     * @param endDate
     * @return
     */
    public static List<String> findDateStrsByUnit(String unit, Date beginDate, Date endDate) {
        List<String> dateList = new ArrayList<>();
        dateList.add(dateToStrByUnit(unit, beginDate));
        Calendar calBegin = Calendar.getInstance();
        // 使用给定的 Date 设置此 Calendar 的时间
        calBegin.setTime(beginDate);
        calBegin.set(calBegin.get(Calendar.YEAR),
                org.apache.commons.lang3.StringUtils.equals("year", unit) ? 2 : calBegin.get(Calendar.MONTH),
                org.apache.commons.lang3.StringUtils.equals("day", unit) ? calBegin.get(Calendar.DAY_OF_MONTH) : 2);

        Calendar calEnd = Calendar.getInstance();
        // 使用给定的 Date 设置此 Calendar 的时间
        calEnd.setTime(endDate);
        calEnd.set(calEnd.get(Calendar.YEAR), org.apache.commons.lang3.StringUtils.equals("year", unit) ? 1 : calEnd.get(Calendar.MONTH),
                org.apache.commons.lang3.StringUtils.equals("day", unit) ? calEnd.get(Calendar.DAY_OF_MONTH) : 1);
        // 测试此日期是否在指定日期之后
        while (calEnd.after(calBegin)) {
            // 根据日历的规则，为给定的日历字段添加或减去指定的时间量
            calBegin.add(getCalendarUnitByUnit(unit), 1);
            dateList.add(dateToStrByUnit(unit, calBegin.getTime()));
        }
        return dateList;
    }

    /**
     * 根据时区和字符串格式获取两个时间戳之间的日期
     * @param unit
     * @param start
     * @param end
     * @param zone
     * @param pattern
     * @return
     */
    //public static List<String> findDateStrByZoneAndPattern(String unit, long start, long end, String zone, String pattern) {
    //    List<String> result = new ArrayList<>();
    //    try {
    //        while (start <= end) {
    //            String day = DateConverter.millToLocalTimeString(start, pattern, zone);
    //            result.add(day);
    //            start = TimestampUtils.calNextTime(start, unit, 1);
    //        }
    //        return result;
    //    } catch (Exception e) {
    //        e.printStackTrace();
    //        return result;
    //    }
    //}



    /**
     * 获取时间范围内的日期
     *
     * @param beginDate
     * @param endDate
     * @return
     */
    public static List<String> findDateStrs(Date beginDate, Date endDate) {
        return findDateStrsByUnit(UNIT_DAY, beginDate, endDate);
    }

    /**
     * 获取当前时间的UTC时间
     *
     * @param startTime
     * @param zone
     * @return
     */
    public static String getCurrentUTCDateTime(String startTime, String zone) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(UTC_DATE_TIME_PATTERN);
        LocalDateTime localDateTime = LocalDateTime.parse(startTime, dateTimeFormatter);
        ZonedDateTime zonedDateTime = localDateTime.toInstant(ZoneOffset.of(zone)).atZone(ZoneId.of("UTC"));
        startTime = zonedDateTime.format(DateTimeFormatter.ofPattern(UTC_DATE_TIME_PATTERN));
        return startTime;
    }

    /**
     * 获取当前时间的UTC时间,timeStamp格式
     *
     * @param startTime
     * @param zone
     * @return
     */
    public static Long getCurrentUTCTimeStamp(String startTime, String zone) {
        return getCurrentUTCTimeStamp(startTime, zone, UTC_DATE_TIME_PATTERN);
    }

    /**
     * 获取当前时间的UTC时间,timeStamp格式
     *
     * @param startTime
     * @param zone
     * @return
     */
    public static Long getCurrentUTCTimeStamp(String startTime, String zone, String pattern) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
        LocalDateTime localDateTime = LocalDateTime.parse(startTime, dateTimeFormatter);
        Long startedAtUtc = localDateTime.toInstant(ZoneOffset.of(zone)).atZone(ZoneId.of("UTC")).toInstant().toEpochMilli();
        return startedAtUtc;
    }

    private static int getCalendarUnitByUnit(String unit) {
        switch (unit) {
            case UNIT_SECOND:
                return Calendar.SECOND;
            case UNIT_HOUR:
                return Calendar.HOUR;
            case UNIT_DAY:
                return Calendar.DAY_OF_MONTH;
            case UNIT_WEEK:
                return Calendar.WEEK_OF_YEAR;
            case UNIT_MONTH:
                return Calendar.MONTH;
            case UNIT_YEAR:
                return Calendar.YEAR;
            default:
                return Calendar.ERA;
        }
    }

    private static long getChronoUnitByUnit(String unit) {
        switch (unit) {
            case UNIT_SECOND:
                return ChronoUnit.SECONDS.getDuration().toMillis();
            case UNIT_MINUTE:
                return ChronoUnit.MINUTES.getDuration().toMillis();
            case UNIT_HOUR:
                return ChronoUnit.HOURS.getDuration().toMillis();
            case UNIT_DAY:
                return ChronoUnit.DAYS.getDuration().toMillis();
            case UNIT_WEEK:
                return ChronoUnit.WEEKS.getDuration().toMillis();
            case UNIT_MONTH:
                return ChronoUnit.MONTHS.getDuration().toMillis();
            case UNIT_YEAR:
                return ChronoUnit.YEARS.getDuration().toMillis();
            default:
                return ChronoUnit.ERAS.getDuration().toMillis();
        }
    }

    public static Long dateStrToEpochMilli(String startTime, String zone) {
        SimpleDateFormat formatter = new SimpleDateFormat(DATE_PATTERN);
        try {
            Date parse = formatter.parse(startTime);
            return parse.getTime();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    //public static List<String> timeRangeToTimeList(long start, long end, String pattern, String zone, long chronoUnit) {
    //    List<String> timeList = new ArrayList<>();
    //    for (long time = start; time <= end; time += chronoUnit) {
    //        timeList.add(DateConverter.millToLocalTimeString(time, pattern, zone));
    //    }
    //    return timeList.stream().distinct().collect(Collectors.toList());
    //}

    public static void main(String[] args) {
        String time = "2019-05-22T03:23:59.000000";
        Long terminateTime = TimestampUtils.getCurrentUTCTimeStamp(time, "Z", UTC_DATE_TIME_PATTERN_000000);

        System.out.println(terminateTime);
        System.out.println(TimestampUtils.dateStrToEpochMilli("2021-08-14", "+8:00"));
    }

    public static long getMillByString(String date, String pattern) throws ParseException {
        SimpleDateFormat format = new SimpleDateFormat(pattern);
        Date parse = format.parse(date);
        return parse.getTime();
    }

    /**
     * @param unit TimestampUtils.unit
     * @return
     */
    public static Date getNextDate(Date date, String unit, int period) {
        return new Date(TimestampUtils.calNextTime(date.getTime(), unit, period));
    }

}
